package ar.uba.fi.algo3.tp1.controlador.generadorfunciones;
import java.util.*;

import ar.uba.fi.algo3.tp1.modelo.excepciones.eSintaxisNoValida;
import ar.uba.fi.algo3.tp1.modelo.funciones.*;
import ar.uba.fi.algo3.tp1.controlador.parser.*;
import ar.uba.fi.algo3.tp1.modelo.funciones.operadores.*;

public class GeneradorFunciones {
	/**
	 * 
	 * Post: devuelve un objeto funcion con lo ingresado por el usuario en caso
	 * 		 de que sea correcta la sintaxis, si no arroja excepcion de sintaxis
	 * 		 no valida 
	 * @param FuncionIngresada: la cadena leida desde la linea de ingreso.
	 * @throws eSintaxisNoValida
	 */
	public Funcion GenerarFuncion(String FuncionIngresada)throws eSintaxisNoValida{
		
		Stack PilaFunciones = new Stack();
		Funcion a,b; 
		String FuncionParseada;
		ConversorAPInversa Parseador = new ConversorAPInversa();
		Funcion f = CrearConstante("0");		
		
		try{
			FuncionParseada = Parseador.parseo(FuncionIngresada);			
		}
		catch (eSintaxisNoValida e){
			throw new eSintaxisNoValida();
		}
		StringTokenizer FuncionTokens=new StringTokenizer(FuncionParseada);
		String TokenEnProceso;
		try{
			while(FuncionTokens.hasMoreTokens()){
				TokenEnProceso=FuncionTokens.nextToken();				
				if(TokenEnProceso.equals("x")){					
					f = CrearIdentidad();
					PilaFunciones.push(f);
				}
				else if ((TokenEnProceso.equals("+")) ||
						 (TokenEnProceso.equals("-")) ||
						 (TokenEnProceso.equals("*")) ||
						 (TokenEnProceso.equals("/")) ||
						 (TokenEnProceso.equals("^"))){
					
					b = (Funcion)PilaFunciones.pop();
					a = (Funcion)PilaFunciones.pop();					
					f = CrearFuncion(a,b,TokenEnProceso);					
					PilaFunciones.push(f);
				}
				else if (TokenEnProceso.equals("log")){
					a = (Funcion)PilaFunciones.pop();
					b = CrearConstante("10");
					f = CrearFuncion(a,b,TokenEnProceso);
					PilaFunciones.push(f);
				}
				else{ // Es un numero
					try {						
						f = CrearConstante(TokenEnProceso);						
						PilaFunciones.push(f);
					}
					catch (eSintaxisNoValida e){
						throw new eSintaxisNoValida();
					}
				}
			}
		}
		catch (eSintaxisNoValida e){
			throw new eSintaxisNoValida();
		}
		catch (EmptyStackException e){
			throw new eSintaxisNoValida();
		}
		return f;
	}
	
	/**
	 * 
	 * Post: crea la funcion indentidad
	 */
	private Funcion CrearIdentidad(){
		Funcion x = new FuncionIdentidad();
		return x;
	}
	
	/**
	 * Post: crea una funcion constante
	 * @param valor
	 * @return
	 * @throws eSintaxisNoValida
	 */
	private Funcion CrearConstante(String valor) throws eSintaxisNoValida{
		
		double valorconstante = 0;
		
		try{
			valorconstante=Double.valueOf(valor).doubleValue();			
		}
		catch(NumberFormatException e){
			throw new eSintaxisNoValida();
		}		
		Funcion constante = new FuncionConstante(valorconstante);
		return constante;
	}
	
	/**
	 * Post: crea una funcion mixta (identidad, operador/es, numero/s)
	 */
	private Funcion CrearFuncion(Funcion f1, Funcion f2, String Operador){		
		
		Operador operador = DefinirOperador(Operador);
		Funcion f = new FuncionCompuesta(f1,f2,operador);
		return f;
	}	
	
	/**
	 * Post: crea una funcion compuesta con la funcion pasada por parametro
	 */	
	private Funcion CrearFuncion(Funcion f1, Funcion f2, String Operador, Funcion f3){
				
		Operador operador = DefinirOperador(Operador);
		Funcion f = new FuncionCompuesta(f1,f2,operador, f3);
		return f;
	}
	
	/**
	 * Post: devuelve el operador pasado como string como objeto Operador
	 *  	 para armar la funcion
	 */
	private Operador DefinirOperador(String oper){
		
		Operador operador;		
		if (oper.equals("+")){
			operador = new OperadorSuma();
		}
		else if (oper.equals("-")){
			operador = new OperadorResta();
		}
		else if (oper.equals("*")){			
			operador = new OperadorProducto();			
		}
		else if (oper.equals("/")){
			operador = new OperadorDivision();
		}
		else if(oper.equals("^")){
			operador = new OperadorPotencia();
		}
		else //Es logaritmo
		{ 
			operador = new OperadorLogaritmo();
		}		
		return operador;
	}	
}
